﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using KgUtils.Resources;

namespace KgUtils.Linq
{
    /// <summary>
    /// LinqHelper
    /// Class from dvp.net http://dvp-net.developpez.com/
    /// </summary>
    public static partial class LinqHelper
    {
        /// <summary>
        /// Obtient le nom d'une propriété à partir d'une <c>Expression&lt;Func&lt;T&gt;&gt;</c>
        /// </summary>
        /// <typeparam name="T">Le type de la propriété à renvoyer (habituellement déterminé par l'inférence de type)</typeparam>
        /// <param name="expression">Une expression qui représente l'accès à une propriété</param>
        /// <returns>Le nom de la propriété accédée par l'expression</returns>
        public static string GetPropertyName<T>(Expression<Func<T>> expression)
        {
            return GetProperty(expression).Name;
        }

        /// <summary>
        /// Obtient les informations d'une propriété à partir d'une <c>Expression&lt;Func&lt;T&gt;&gt;</c>
        /// </summary>
        /// <typeparam name="T">Le type de la propriété à renvoyer (habituellement déterminé par l'inférence de type)</typeparam>
        /// <param name="expression">Une expression qui représente l'accès à une propriété</param>
        /// <returns>Les informations de la propriété accédée par l'expression</returns>
        public static PropertyInfo GetProperty<T>(Expression<Func<T>> expression)
        {
            var mExpr = expression.Body as MemberExpression;
            if (mExpr == null)
                throw new ArgumentException(Messages.ExpressionMustBePropertyAccess);
            return (PropertyInfo)mExpr.Member;
        }

        /// <summary>
        /// Obtient le nom d'une méthode à partir d'une <c>Expression&lt;Func&lt;T&gt;&gt;</c>
        /// </summary>
        /// <typeparam name="T">Le type de retour de la méthode à renvoyer (habituellement déterminé par l'inférence de type)</typeparam>
        /// <param name="expression">Une expression qui représente l'appel à une méthode</param>
        /// <returns>Le nom de la méthode appelée par l'expression</returns>
        public static string GetMethodName<T>(Expression<Func<T>> expression)
        {
            return GetMethod(expression).Name;
        }

        /// <summary>
        /// Obtient les informations d'une méthode à partir d'une <c>Expression&lt;Func&lt;T&gt;&gt;</c>
        /// </summary>
        /// <typeparam name="T">Le type de retour de la méthode à renvoyer (habituellement déterminé par l'inférence de type)</typeparam>
        /// <param name="expression">Une expression qui représente l'appel à une méthode</param>
        /// <returns>Les informations de la méthode appelée par l'expression</returns>
        public static MethodInfo GetMethod<T>(Expression<Func<T>> expression)
        {
            var callExpr = expression.Body as MethodCallExpression;
            if (callExpr == null)
                throw new ArgumentException(Messages.ExpressionMustBeMethodCall);
            return callExpr.Method;
        }

        /// <summary>
        /// Obtient le nom d'une méthode à partir d'une <c>Expression&lt;Action&gt;</c>
        /// </summary>
        /// <param name="expression">Une expression qui représente l'appel à une méthode</param>
        /// <returns>Le nom de la méthode appelée par l'expression</returns>
        public static string GetMethodName(Expression<Action> expression)
        {
            return GetMethod(expression).Name;
        }

        /// <summary>
        /// Obtient les informations d'une méthode à partir d'une <c>Expression&lt;Action&gt;</c>
        /// </summary>
        /// <param name="expression">Une expression qui représente l'appel à une méthode</param>
        /// <returns>Les  informations de la méthode appelée par l'expression</returns>
        public static MethodInfo GetMethod(Expression<Action> expression)
        {
            var callExpr = expression.Body as MethodCallExpression;
            if (callExpr == null)
                throw new ArgumentException(Messages.ExpressionMustBeMethodCall);
            return callExpr.Method;
        }

        /// <summary>
        /// Permet de récupérer le nom d'une propriété d'une classe dont on a l'instance.
        /// Utilisation : var a = new Animal() a.MemberName(x => x.Status)
        /// See : http://stackoverflow.com/questions/1407576/accessing-c-sharp-property-name-or-attributes
        /// </summary>
        public static string MemberName<T, R>(this T obj, Expression<Func<T, R>> expr)
        {
            var node = expr.Body as MemberExpression;
            if (object.ReferenceEquals(null, node))
                throw new InvalidOperationException("Expression must be of member access");
            return node.Member.Name;
        }

        /// <summary>
        /// Permet de récupérer le nom d'une propriété d'une classe lorsqu'on n'a pas d'instance.
        /// Utilisation : MembersOf<Animal>.GetName(x => x.Status)
        /// See : http://stackoverflow.com/questions/1407576/accessing-c-sharp-property-name-or-attributes
        /// </summary>
        public static class MembersOf<T>
        {
            public static string GetName<R>(Expression<Func<T, R>> expr)
            {
                var node = expr.Body as MemberExpression;
                if (object.ReferenceEquals(null, node))
                    throw new InvalidOperationException("Expression must be of member access");
                return node.Member.Name;
            }
        }

        #region Expr and Func helpers

        /// <summary>
        /// Renvoie l'expression passée en paramètre, de façon à profiter de l'inférence de type
        /// </summary>
        /// <typeparam name="T">Le type de retour de l'expression</typeparam>
        /// <param name="expr">L'expression dont on veut inférer le type</param>
        /// <returns>L'expression passée en paramètre</returns>
        public static Expression<Func<T>> Expr<T>(Expression<Func<T>> expr)
        {
            return expr;
        }

        /// <summary>
        /// Renvoie l'expression passée en paramètre, de façon à profiter de l'inférence de type
        /// </summary>
        /// <typeparam name="T">Le type du paramètre de l'expression</typeparam>
        /// <typeparam name="TResult">Le type de retour de l'expression</typeparam>
        /// <param name="expr">L'expression dont on veut inférer le type</param>
        /// <returns>L'expression passée en paramètre</returns>
        public static Expression<Func<T, TResult>> Expr<T, TResult>(Expression<Func<T, TResult>> expr)
        {
            return expr;
        }

        /// <summary>
        /// Renvoie l'expression passée en paramètre, de façon à profiter de l'inférence de type
        /// </summary>
        /// <typeparam name="T">Le type du paramètre de l'expression</typeparam>
        /// <typeparam name="TResult">Le type de retour de l'expression</typeparam>
        /// <param name="dummyArg">Objet utilisé comme modèle pour déterminer le type du paramètre</param>
        /// <param name="expr">L'expression dont on veut inférer le type</param>
        /// <returns>L'expression passée en paramètre</returns>
        public static Expression<Func<T, TResult>> Expr<T, TResult>(T dummyArg, Expression<Func<T, TResult>> expr)
        {
            return expr;
        }

        /// <summary>
        /// Renvoie la fonction passée en paramètre, de façon à profiter de l'inférence de type
        /// </summary>
        /// <typeparam name="T">Le type de retour de la fonction</typeparam>
        /// <param name="func">La fonction dont on veut inférer le type</param>
        /// <returns>La fonction passée en paramètre</returns>
        public static Func<T> Func<T>(Func<T> func)
        {
            return func;
        }

        /// <summary>
        /// Renvoie la fonction passée en paramètre, de façon à profiter de l'inférence de type
        /// </summary>
        /// <typeparam name="T">Le type du paramètre de la fonction</typeparam>
        /// <typeparam name="TResult">Le type de retour de la fonction</typeparam>
        /// <param name="func">La fonction dont on veut inférer le type</param>
        /// <returns>La fonction passée en paramètre</returns>
        public static Func<T, TResult> Func<T, TResult>(Func<T, TResult> func)
        {
            return func;
        }

        /// <summary>
        /// Renvoie la fonction passée en paramètre, de façon à profiter de l'inférence de type
        /// </summary>
        /// <typeparam name="T">Le type du paramètre de la fonction</typeparam>
        /// <typeparam name="TResult">Le type de retour de la fonction</typeparam>
        /// <param name="dummyArg">Objet utilisé comme modèle pour déterminer le type du paramètre</param>
        /// <param name="func">La fonction dont on veut inférer le type</param>
        /// <returns>La fonction passée en paramètre</returns>
        public static Func<T, TResult> Func<T, TResult>(T dummyArg, Func<T, TResult> func)
        {
            return func;
        }

        #endregion
    }
}
